/*
 * Copyright (c) 2006-2022, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author            Notes
 * 2021-02-25     iysheng           first version
 * 2022-05-03     BruceOu           optimization adc
 * 2025-08-28     community         add DMA multi-channel support
 */

#include "drv_adc.h"
#include <gd32f4xx.h>

#define DBG_TAG             "drv.adc"
#define DBG_LVL             DBG_INFO

#include <rtdbg.h>

#ifdef RT_USING_ADC

#if defined(BSP_USING_ADC0)
struct rt_adc_device adc0;
#endif

#if defined(BSP_USING_ADC1)
struct rt_adc_device adc1;
#endif

#if defined(BSP_USING_ADC2)
struct rt_adc_device adc2;
#endif

#define MAX_EXTERN_ADC_CHANNEL    16

/* ------------------  1. 静态配置表  ------------------ */
static struct gd32_adc adc_obj[] = {
#ifdef BSP_USING_ADC0
    {
        ADC0,
        RCU_ADC0,
        {
            GET_PIN(A, 0), GET_PIN(A, 1), GET_PIN(A, 2), GET_PIN(A, 3),
            GET_PIN(A, 4), GET_PIN(A, 5), GET_PIN(A, 6), GET_PIN(A, 7),
            GET_PIN(B, 0), GET_PIN(B, 1), GET_PIN(C, 0), GET_PIN(C, 1),
            GET_PIN(C, 2), GET_PIN(C, 3), GET_PIN(C, 4), GET_PIN(C, 5),
        },
        &adc0,
        "adc0",
    },
#endif

#ifdef BSP_USING_ADC1
#ifdef BSP_ADC1_DMA_ENABLE
    {
        .adc_periph        = ADC1,
        .adc_clk           = RCU_ADC1,
        .adc_pins          = {
            GET_PIN(A, 0), GET_PIN(A, 1), GET_PIN(A, 2), GET_PIN(A, 3),
            GET_PIN(A, 4), GET_PIN(A, 5), GET_PIN(A, 6), GET_PIN(A, 7),
            GET_PIN(B, 0), GET_PIN(B, 1), GET_PIN(C, 0), GET_PIN(C, 1),
            GET_PIN(C, 2), GET_PIN(C, 3), GET_PIN(C, 4), GET_PIN(C, 5),
        },
        .adc               = &adc1,
        .device_name       = "adc1",
        .dma_channels      = BSP_ADC1_DMA_CHANNELS,
        .dma_channel_num   = BSP_ADC1_DMA_CHANNEL_NUM,
        .dma_enable        = RT_TRUE,
        .dma_periph_clock  = RCU_DMA1,
        .dma_periph        = DMA1,
        .dma_channel       = DMA_CH2,
    },
#else
    {
        ADC1,
        RCU_ADC1,
        {
            GET_PIN(A, 0), GET_PIN(A, 1), GET_PIN(A, 2), GET_PIN(A, 3),
            GET_PIN(A, 4), GET_PIN(A, 5), GET_PIN(A, 6), GET_PIN(A, 7),
            GET_PIN(B, 0), GET_PIN(B, 1), GET_PIN(C, 0), GET_PIN(C, 1),
            GET_PIN(C, 2), GET_PIN(C, 3), GET_PIN(C, 4), GET_PIN(C, 5),
        },
        &adc1,
        "adc1",
    },
#endif
#endif

#ifdef BSP_USING_ADC2
#ifdef BSP_ADC2_DMA_ENABLE
    {
        .adc_periph        = ADC2,
        .adc_clk           = RCU_ADC2,
        .adc_pins          = {
            GET_PIN(A, 0), GET_PIN(A, 1), GET_PIN(A, 2), GET_PIN(A, 3),
            GET_PIN(A, 4), GET_PIN(A, 5), GET_PIN(A, 6), GET_PIN(A, 7),
            GET_PIN(B, 0), GET_PIN(B, 1), GET_PIN(C, 0), GET_PIN(C, 1),
            GET_PIN(C, 2), GET_PIN(C, 3), GET_PIN(C, 4), GET_PIN(C, 5),
            /* 通道9 -> PF3 */
            [9] = GET_PIN(F, 3),
        },
        .adc               = &adc2,
        .device_name       = "adc2",
        .dma_channels      = BSP_ADC2_DMA_CHANNELS,
        .dma_channel_num   = BSP_ADC2_DMA_CHANNEL_NUM,
        .dma_enable        = RT_TRUE,
        .dma_periph_clock  = RCU_DMA1,
        .dma_periph        = DMA1,
        .dma_channel       = DMA_CH1,
    },
#else
    {
        ADC2,
        RCU_ADC2,
        {
            GET_PIN(A, 0), GET_PIN(A, 1), GET_PIN(A, 2), GET_PIN(A, 3),
            GET_PIN(A, 4), GET_PIN(A, 5), GET_PIN(A, 6), GET_PIN(A, 7),
            GET_PIN(B, 0), GET_PIN(B, 1), GET_PIN(C, 0), GET_PIN(C, 1),
            GET_PIN(C, 2), GET_PIN(C, 3), GET_PIN(C, 4), GET_PIN(C, 5),
        },
        &adc2,
        "adc2",
    },
#endif
#endif
};

/* ------------------  2. 普通单通道实现（原有）  ------------------ */
/**
* @brief ADC MSP Initialization
*        This function configures the hardware resources.
* @param adc_clk, pin
* @retval None
*/
static void gd32_adc_gpio_init(rcu_periph_enum adc_clk, rt_base_t pin)
{
    /* enable ADC clock */
    rcu_periph_clock_enable(adc_clk);

#if defined SOC_SERIES_GD32F4xx
    /* configure adc pin */
    gpio_mode_set(PIN_GDPORT(pin), GPIO_MODE_ANALOG, GPIO_PUPD_NONE, PIN_GDPIN(pin));
#else
    /* configure adc pin */
    gpio_init(PIN_GDPORT(pin), GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, PIN_GDPIN(pin));

#endif
}

/**
* @brief ADC enable
*        This function enable adc.
* @param device, channel, enabled
* @retval None
*/
static rt_err_t gd32_adc_enabled(struct rt_adc_device *device, rt_int8_t channel, rt_bool_t enabled)
{
    uint32_t adc_periph;
    struct gd32_adc *adc = (struct gd32_adc *)device->parent.user_data;

    if (channel >= MAX_EXTERN_ADC_CHANNEL)
    {
        LOG_E("invalid channel");
        return -RT_EINVAL;
    }

    adc_periph = (uint32_t)(adc->adc_periph);

    if (enabled == ENABLE)
    {
        gd32_adc_gpio_init(adc->adc_clk, adc->adc_pins[channel]);

        adc_data_alignment_config(adc_periph, ADC_DATAALIGN_RIGHT);

        #if defined SOC_SERIES_GD32F4xx
        adc_channel_length_config(adc_periph, ADC_ROUTINE_CHANNEL, 1);
        adc_external_trigger_source_config(adc_periph, ADC_ROUTINE_CHANNEL, ADC_EXTTRIG_ROUTINE_EXTI_11);
        adc_external_trigger_config(adc_periph, ADC_ROUTINE_CHANNEL, ENABLE);
        #else
        adc_channel_length_config(adc_periph, ADC_REGULAR_CHANNEL, 1);
        adc_external_trigger_source_config(adc_periph, ADC_REGULAR_CHANNEL, ADC0_1_2_EXTTRIG_REGULAR_NONE);
        adc_external_trigger_config(adc_periph, ADC_REGULAR_CHANNEL, ENABLE);
        #endif

        adc_enable(adc_periph);
        rt_hw_us_delay(1);

        /* ADC calibration and reset calibration */
        adc_calibration_enable(adc_periph);
    }
    else
    {
        adc_disable(adc_periph);
    }
    return RT_EOK;
}

/**
* @brief convert adc.
*        This function get adc value.
* @param device, channel, value
* @retval None
*/
static rt_err_t gd32_adc_convert(struct rt_adc_device *device, rt_int8_t channel, rt_uint32_t *value)
{
    uint32_t adc_periph;
    uint32_t timeout = 0;
    struct gd32_adc *adc = (struct gd32_adc *)(device->parent.user_data);

    if (!value)
    {
        LOG_E("invalid param");
        return -RT_EINVAL;
    }

    adc_periph = (uint32_t)(adc->adc_periph);
    adc_flag_clear(adc_periph, ADC_FLAG_EOC | ADC_FLAG_STRC);
#if defined SOC_SERIES_GD32F4xx
    adc_routine_channel_config(adc_periph, 0, channel, ADC_SAMPLETIME_480);
    adc_software_trigger_enable(adc_periph, ADC_ROUTINE_CHANNEL);
#else
    adc_regular_channel_config(adc_periph, 0, channel, ADC_SAMPLETIME_13POINT5);
    adc_software_trigger_enable(adc_periph, ADC_REGULAR_CHANNEL);
#endif

    while (!adc_flag_get(adc_periph, ADC_FLAG_EOC))
    {
        if(timeout >= 100)
        {
            adc_flag_clear(adc_periph, ADC_FLAG_EOC | ADC_FLAG_STRC);
            LOG_E("Convert Timeout");
            return -RT_ETIMEOUT;
        }
        else
        {
            timeout++;
            rt_hw_us_delay(1);
        }
    }

#if defined SOC_SERIES_GD32F4xx
    *value = adc_routine_data_read(adc_periph);
#else
    *value = adc_regular_data_read(adc_periph);
#endif

    adc_flag_clear(adc_periph, ADC_FLAG_EOC | ADC_FLAG_STRC);
    return RT_EOK;
}

static const struct rt_adc_ops gd32_adc_ops = {
    .enabled = gd32_adc_enabled,
    .convert = gd32_adc_convert,
};

/* ------------------  3. DMA 初始化  ------------------ */
#ifdef RT_USING_ADC_DMA
static void gd32_adc_dma_init(struct gd32_adc *adc)
{
    /* --- 0. 时钟 & 系统 --- */
    rcu_periph_clock_enable(adc->dma_periph_clock);
    adc_clock_config(ADC_ADCCK_PCLK2_DIV4);   /* 30 MHz */

    /* 1. GPIO */
    for (int i = 0; i < adc->dma_channel_num; i++)
        gd32_adc_gpio_init(adc->adc_clk, adc->adc_pins[adc->dma_channels[i]]);

    /* 2. ADC scan mode */
    /* ADC mode config */
    adc_sync_mode_config(ADC_SYNC_MODE_INDEPENDENT);
    /* ADC contineous function disable */
    adc_special_function_config(adc->adc_periph, ADC_CONTINUOUS_MODE, ENABLE);
    /* ADC scan mode disable */
    adc_special_function_config(adc->adc_periph, ADC_SCAN_MODE, ENABLE);
    /* ADC data alignment config */
    adc_data_alignment_config(adc->adc_periph, ADC_DATAALIGN_RIGHT);
    /* ADC channel length config */
    adc_channel_length_config(adc->adc_periph, ADC_ROUTINE_CHANNEL, adc->dma_channel_num);
    /* ADC routine channel config */
    for (int i = 0; i < adc->dma_channel_num; i++)
    {
        #if defined SOC_SERIES_GD32F4xx
            adc_routine_channel_config(adc->adc_periph, i, adc->dma_channels[i], ADC_SAMPLETIME_480);
        #else
            adc_regular_channel_config(adc->adc_periph, i, adc->dma_channels[i], ADC_SAMPLETIME_13POINT5);
        #endif
    }

    /* 3. DMA circular */
    dma_single_data_parameter_struct dma_init_struct;
    memset(&dma_init_struct, 0, sizeof(dma_init_struct));
    /* ADC DMA_channel configuration */
    dma_deinit(adc->dma_periph, adc->dma_channel);
    /* initialize DMA single data mode */
    dma_init_struct.direction    = DMA_PERIPH_TO_MEMORY;
    dma_init_struct.memory0_addr  = (uint32_t)adc->dma_buffer;
    dma_init_struct.memory_inc   = DMA_MEMORY_INCREASE_ENABLE;
    dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_32BIT;
    dma_init_struct.number       = ADC_DMA_BUF_LEN;
    dma_init_struct.periph_addr  = (uint32_t)&ADC_RDATA(adc->adc_periph);
    dma_init_struct.periph_inc   = DMA_PERIPH_INCREASE_DISABLE;
    dma_init_struct.priority     = DMA_PRIORITY_HIGH;
    dma_init_struct.circular_mode = DMA_CIRCULAR_MODE_ENABLE;
    dma_single_data_mode_init(adc->dma_periph, adc->dma_channel, &dma_init_struct);
    dma_channel_subperipheral_select(adc->dma_periph, adc->dma_channel, DMA_SUBPERI1);
    /* enable DMA circulation mode */
    dma_circulation_enable(adc->dma_periph, adc->dma_channel);
    /* enable DMA channel */
    dma_channel_enable(adc->dma_periph, adc->dma_channel);

    /* 4. ADC DMA and start */
    /* ADC trigger config */
    // adc_external_trigger_source_config(adc->adc_periph, ADC_ROUTINE_CHANNEL, ADC_EXTTRIG_ROUTINE_T0_CH0);
    adc_external_trigger_config(adc->adc_periph, ADC_ROUTINE_CHANNEL, EXTERNAL_TRIGGER_DISABLE);
    /* ADC DMA function enable */
    adc_dma_request_after_last_enable(adc->adc_periph);
    adc_dma_mode_enable(adc->adc_periph);
    /* enable ADC interface */
    adc_enable(adc->adc_periph);
    /* wait for ADC stability */
    rt_hw_us_delay(1);
    /* ADC calibration and reset calibration */
    adc_calibration_enable(adc->adc_periph);
    /* enable ADC software trigger */
    adc_software_trigger_enable(adc->adc_periph, ADC_ROUTINE_CHANNEL);
}
#endif

/* ------------------  4. 注册入口  ------------------ */
static int rt_hw_adc_init(void)
{
    int ret, i = 0;

    for (; i < sizeof(adc_obj) / sizeof(adc_obj[0]); i++)
    {
        ret = rt_hw_adc_register(adc_obj[i].adc, \
            (const char *)adc_obj[i].device_name, \
            &gd32_adc_ops, &adc_obj[i]);
        if (ret != RT_EOK)
        {
            /* TODO err handler */
            LOG_E("failed register %s, err=%d", adc_obj[i].device_name, ret);
        }
#ifdef RT_USING_ADC_DMA
        if (adc_obj[i].dma_enable)
            gd32_adc_dma_init(&adc_obj[i]);
#endif
    }

    return ret;
}
INIT_BOARD_EXPORT(rt_hw_adc_init);

/* ------------------  5. Custom API：DMA最新值  ------------------ */
#ifdef RT_USING_ADC_DMA
rt_uint32_t rt_adc_dma_read(const char *name, rt_uint8_t index)
{
    for (int i = 0; i < sizeof(adc_obj) / sizeof(adc_obj[0]); i++) {
        if (rt_strcmp(adc_obj[i].device_name, name) == 0 &&
            adc_obj[i].dma_enable &&
            index < adc_obj[i].dma_channel_num) {
            int offset = (ADC_DMA_BUF_LEN - adc_obj[i].dma_channel_num) + index;
            return adc_obj[i].dma_buffer[offset];
        }
    }
    return 0;
}

rt_uint32_t rt_adc_dma_avg(const char *name,
                           rt_uint8_t index,
                           rt_uint8_t *valid_samples)
{
    for (int i = 0; i < sizeof(adc_obj) / sizeof(adc_obj[0]); i++) {
        struct gd32_adc *adc = &adc_obj[i];
        if (rt_strcmp(adc->device_name, name) != 0 || !adc->dma_enable ||
            index >= adc->dma_channel_num)
            continue;

        uint32_t sum = 0;
        uint8_t  cnt = 0;
        uint8_t  step = adc->dma_channel_num;

        /* 按交错步长累加同一通道的所有样本 */
        for (int j = index; j < ADC_DMA_BUF_LEN; j += step) {
            sum += adc->dma_buffer[j];
            cnt++;
        }

        if (valid_samples)
            *valid_samples = cnt;

        return cnt ? (sum / cnt) : 0;
    }
    return 0;
}
#endif

#endif /* RT_USING_ADC */